import logging
import logging.config
import os
from collections import defaultdict,OrderedDict

from git import Repo
from pydriller import Git
from tqdm import tqdm
import time
from subprocess import run, CalledProcessError
from build_feature.extract_npm_different_version_functions.core.function_diff_info import FunctionDiffInfo
from build_feature.extract_npm_different_version_functions.core.function_info import FunctionInfo
from build_feature.extract_npm_different_version_functions.core.function_info_extractor import FunctionInfoExtractor
from build_feature.extract_npm_different_version_functions.core.modified_file_analyzer import ModifiedFileAnalyzer
from build_feature.extract_npm_different_version_functions.utils.common_utils import is_not_anonymous

from build_feature.extract_npm_different_version_functions.core.function_diff_info import FunctionDifferenceType

# 加载配置文件
logging.config.fileConfig(os.path.join(os.path.dirname(__file__), '..', 'logging.conf'))

# 获取日志记录器
logger = logging.getLogger("root")


class GitRepository:

    def __init__(self, project, function_info_extractor: FunctionInfoExtractor):
        path = project["path"]
        self.path = project["path"]
        self.path_base_name = os.path.basename(project["path"])
        if project.get("type") == "standalone":
            self.library = self.path_base_name
        else:
            self.library = f"{project.get('org_name', '')}/{self.path_base_name}"
        self.git = Git(path)
        self.repo = Repo(path)
        self.tags = sorted(self.repo.tags, key=lambda t: t.commit.committed_datetime)
        self.version_to_tag = {tag.name: tag for tag in self.tags}
        self.init_commit = self.repo.head.commit
        self.version_info_dict = dict()
        self.function_info_extractor = function_info_extractor
        self.history_no_diff_functions = None
        self.js_output_path = None
    def get_tags(self):
        return self.tags

    def diff(self, from_commit_id: str, to_commit_id: str):
        return self.git.diff(from_commit_id, to_commit_id)

    def get_version_modified_file(self, from_tag, to_tag):
        return self.diff(from_tag.commit.hexsha, to_tag.commit.hexsha)

    # def hard_reset(self, commit_id):
    #     self.repo.git.reset(commit_id, hard=True)
    def hard_reset(self, commit_id, retries=3):
        for i in range(retries):
            try:
                self.repo.git.reset(commit_id, hard=True)
                return True
            except Exception as e:
                lock_file_path = os.path.join(self.path, ".git/index.lock")
                if os.path.exists(lock_file_path):
                    os.remove(lock_file_path)
                    time.sleep(0.5)  # 等待文件系统释放
        return False

    def analysis_version_modified_function(self, from_tag, to_tag):
        self.__generate_version_all_functions([from_tag, to_tag])
        if self.version_info_dict[to_tag.name] is None or self.version_info_dict[from_tag.name] is None:
            return None
        modified_files = self.get_version_modified_file(from_tag, to_tag)
        modified_function = []
        version_no_modified_function = set()
        for modified_file in modified_files:
            # 准备数据
            modified_file_analyzer = ModifiedFileAnalyzer(modified_file)
            new_path, old_path = modified_file_analyzer.get_modified_file_path()
            from_version_file_info = (self.version_info_dict[from_tag.name].get(old_path, []))
            to_version_file_info = (self.version_info_dict[to_tag.name].get(new_path, []))
            if len(from_version_file_info) == 0 and len(to_version_file_info) == 0:
                continue
            filter_from_version_file_info = self.filter_version_file_info(from_version_file_info)
            filter_to_version_file_info = self.filter_version_file_info(to_version_file_info)
            # 处理函数
            modified_file_analyzer.analyze()
            # 现在使用的是没有进行过滤的完整函数，包含嵌套函数（存储两次）
            functions,from_no_diff_function_info = self.__analysis_function(modified_file_analyzer, filter_from_version_file_info,
                                                 filter_to_version_file_info)
            modified_function.extend(functions)
            version_no_modified_function.update(from_no_diff_function_info)
        # 存储所有版本都没有发生变化的函数信息
        if self.history_no_diff_functions is not None:
            self.history_no_diff_functions = self.history_no_diff_functions.intersection (version_no_modified_function)
        else:
            self.history_no_diff_functions = version_no_modified_function
        return modified_function

    def __analysis_function(self, modified_file_analyzer, from_version_file_info, to_version_file_info):
        add_function_info_list = []
        delete_function_info_list = []

        add_function_diff_info_list = []
        delete_function_diff_info_list = []
        update_function_diff_info_list = []
        match_before_function_location_set = set()
        match_after_function_location_set = set()

        add_blocks = modified_file_analyzer.get_add_blocks()
        delete_blocks = modified_file_analyzer.get_delete_blocks()

        for function_info in to_version_file_info:
            start_line = function_info.get_start_line_number()
            end_line = function_info.get_end_line_number()
            for index, block in enumerate(add_blocks):
                if block[0] > end_line:
                    break
                if ModifiedFileAnalyzer.is_block_complete_covered_function(start_line, end_line, block):
                    # 添加覆盖整个函数，视为新增
                    add_function_info_list.append(function_info)
                    match_after_function_location_set.add(function_info.get_location())
                    break
                elif ModifiedFileAnalyzer.is_block_part_covered_function(start_line, end_line, block):
                    # 添加覆盖部分函数，视为修改
                    before_function = self.get_version_map_function_by_no_modify_lines("new2old",
                                                                                       modified_file_analyzer,
                                                                                       function_info,
                                                                                       from_version_file_info,
                                                                                       add_blocks[index:])
                    if before_function is None or before_function.get_location() in match_before_function_location_set:
                        # 旧函数为空或已经被匹配，函数实际是新增 -> 由于diff解析问题，diff未包含整个函数，从而被误判为修改
                        add_function_info_list.append(function_info)
                        match_after_function_location_set.add(function_info.get_location())
                        break
                    # 函数体相关修改和函数名修改
                    if ((not function_info.has_same_function_body(before_function)) or
                            (function_info.get_name() != before_function.get_name())):
                        update_function_diff_info_list.append(
                            FunctionDiffInfo.new_by_update(before_function, function_info))
                        match_after_function_location_set.add(function_info.get_location())
                        match_before_function_location_set.add(before_function.get_location())
                    break
        for function_info in from_version_file_info:
            if function_info.get_location() in match_before_function_location_set:
                continue
            start_line = function_info.get_start_line_number()
            end_line = function_info.get_end_line_number()
            for index, block in enumerate(delete_blocks):
                if block[0] > end_line:
                    break
                if ModifiedFileAnalyzer.is_block_complete_covered_function(start_line, end_line, block):
                    delete_function_info_list.append(function_info)
                    break
                elif ModifiedFileAnalyzer.is_block_part_covered_function(start_line, end_line, block):
                    # 删除覆盖部分函数，视为修改
                    after_function = self.get_version_map_function_by_no_modify_lines("old2new",
                                                                                      modified_file_analyzer,
                                                                                      function_info,
                                                                                      to_version_file_info,
                                                                                      delete_blocks[index:])
                    if after_function is None or after_function.get_location() in match_after_function_location_set:
                        # 新函数为空或已经被匹配，函数实际是删除 -> 由于diff解析问题，diff未包含整个函数，从而被误判为修改
                        delete_function_info_list.append(function_info)
                        break
                    # 函数体相关修改和函数名修改
                    if ((not function_info.has_same_function_body(after_function)) or
                            (function_info.get_name() != after_function.get_name())):
                        update_function_diff_info_list.append(
                            FunctionDiffInfo.new_by_update(function_info, after_function))
                        match_after_function_location_set.add(after_function.get_location())
                    break
        # 处理由于交换函数位置导致函数同时添加和删除
        # 依据函数名判断
        # 筛选非匿名函数并创建字典
        add_func_dict = {func.get_signature(): func for func in filter(is_not_anonymous, add_function_info_list)}
        delete_func_dict = {func.get_signature(): func for func in filter(is_not_anonymous, delete_function_info_list)}
        # 找出共同的函数签名
        common_func_signatures = set(add_func_dict.keys()) & set(delete_func_dict.keys())
        for signature in common_func_signatures:
            add_func = add_func_dict[signature]
            delete_func = delete_func_dict[signature]
            add_function_info_list.remove(add_func)
            delete_function_info_list.remove(delete_func)
            if not add_func.has_same_function_body(delete_func):
                update_function_diff_info_list.append(FunctionDiffInfo.new_by_update(delete_func, add_func))
        for add_func in add_function_info_list:
            add_function_diff_info_list.append(FunctionDiffInfo.new_by_add(add_func))
        for delete_func in delete_function_info_list:
            delete_function_diff_info_list.append(FunctionDiffInfo.new_by_delete(delete_func))
        function_diff_info_list = []
        function_diff_info_list.extend(delete_function_diff_info_list)
        function_diff_info_list.extend(update_function_diff_info_list)
        function_diff_info_list.extend(add_function_diff_info_list)
        no_diff_function_info_list = from_version_file_info

        for function_diff_info in function_diff_info_list:
            diff_type = function_diff_info.get_diff_type()
            if diff_type == FunctionDifferenceType.DELETE:
                no_diff_function_info_list.remove(function_diff_info.get_from_function_info())
            elif diff_type == FunctionDifferenceType.UPDATE:
                no_diff_function_info_list.remove(function_diff_info.get_from_function_info())
        return function_diff_info_list, no_diff_function_info_list

    @staticmethod
    def get_version_map_function_by_no_modify_lines(map_type, modified_file_analyzer: ModifiedFileAnalyzer,
                                                    function_info: FunctionInfo,
                                                    version_file_info, block_list):
        count_candidate_map_function_dict = defaultdict(int)
        start_line = function_info.get_start_line_number()
        end_line = function_info.get_end_line_number()
        no_modify_lines = ModifiedFileAnalyzer.find_no_modify_lines(start_line, end_line, block_list)
        if map_type == "new2old":
            map_no_modify_lines = [modified_file_analyzer.map_new_to_old_line(line) for line in no_modify_lines if
                                   function_info.is_effective_code_line_content(
                                       modified_file_analyzer.get_new_line_content(line))]
        else:
            map_no_modify_lines = [modified_file_analyzer.map_old_to_new_line(line) for line in no_modify_lines if
                                   function_info.is_effective_code_line_content(
                                       modified_file_analyzer.get_old_line_content(line))]
        need_same_function_signature = False
        if len(map_no_modify_lines) == 0:
            # 如果没有找到有效映射行，则考虑所有映射行，同时使用函数签名进行判断
            if function_info.is_anonymous_function():
                # 需要函数签名作为判断依据，但是函数是匿名函数无法作为判断依据，直接返回None
                return None
            need_same_function_signature = True
            map_no_modify_lines = [modified_file_analyzer.map_new_to_old_line(line) for line in no_modify_lines if
                                   modified_file_analyzer.get_new_line_content(line)]
        for map_no_modify_line in map_no_modify_lines:
            match_function_info, index = FunctionInfo.match_function_info(map_no_modify_line, map_no_modify_line,
                                                                          version_file_info)
            if match_function_info is None or len(match_function_info) == 0:
                continue
            cur_index = index[0]
            count_candidate_map_function_dict[cur_index] += 1
        map_function_info_index = -1
        max_map_count = 0
        for function_info_index, map_count in count_candidate_map_function_dict.items():
            if map_count > max_map_count:
                if need_same_function_signature and version_file_info[
                    map_function_info_index].get_signature() != function_info.get_signature():
                    continue
                map_function_info_index = function_info_index
                max_map_count = map_count
        return version_file_info[map_function_info_index] if map_function_info_index != -1 else None

    def __generate_version_all_functions(self, tags: list):
        logging.debug("Generating version all functions")
        for tag in tags:
            if tag.name in self.version_info_dict.keys():
                continue
            order_file_functions_info = self.analysis_tag_version_all_functions(tag)
            self.version_info_dict[tag.name] = order_file_functions_info

    def analysis_tag_version_all_functions(self, tag):
        try:
            # 回退版本
            logging.debug(f"版本回退:{tag.name}")
            self.hard_reset(tag.commit.hexsha)
        except Exception as e:
            logging.error("获取{}版本信息失败:{}".format(tag.name, e))
            return None
        # 获取当前版本函数信息
        logging.debug("获取版本函数信息")
        src_functions_info = self.analysis_functions_info(self.path)
        # 依据函数所在文件组织函数
        file_functions_info = defaultdict(list)
        for function_info in src_functions_info:
            if function_info.get_library_name() == self.path_base_name:
                file_relative_path = function_info.get_inner_relative_path()
            else:
                file_relative_path = function_info.get_relative_path()
            file_functions_info[file_relative_path].append(function_info)
        # 依据文件中函数开始行数排序
        for file_relative_path, functions_info in file_functions_info.items():
            functions_info.sort(key=lambda f_i: f_i.get_start_line_number())
        return file_functions_info

    @staticmethod
    def filter_version_file_info(version_file_info):
        filter_version_file_info = []
        version_file_info_set = set()
        for function_info in version_file_info:
            function_start_line_number = function_info.get_start_line_number()
            function_end_line_number = function_info.get_end_line_number()
            src_function_list, _ = FunctionInfo.match_function_info(function_start_line_number,
                                                                    function_end_line_number, version_file_info)
            src_function = FunctionInfo.get_src_function(src_function_list)
            if src_function in version_file_info_set:
                continue
            version_file_info_set.add(src_function)
            filter_version_file_info.append(function_info)
        return filter_version_file_info

    def analysis_functions_info(self, path):
        return self.function_info_extractor.extract(path,self.js_output_path,self.library)

    def analysis_all_version_modified_function(self, max_tag_num=10):
        logging.debug('Generating adjacent version pairs changed methods...')
        # Initialize Git repository to get tags
        tags = self.tags
        if max_tag_num <= len(tags):
            tags = tags[-max_tag_num:]
        modified_functions =OrderedDict()
        # bar = tqdm(range(len(tags) - 1))
        for i in range(len(tags) - 1):
            pre_tag_name = tags[i].name
            next_tag_name = tags[i + 1].name
            version_pair = f"{pre_tag_name}@{next_tag_name}"
            # bar.set_postfix_str(version_pair)
            from_tag = tags[i]
            to_tag = tags[i + 1]
            modified_functions[version_pair] = self.analysis_version_modified_function(from_tag, to_tag)
        return modified_functions

    def reset_init_commit(self):
        self.hard_reset(self.init_commit)

    # def __del__(self):
    #     try:
    #         self.reset_init_commit()
    #         logger.debug("还原源代码状态")
    #     except Exception:
    #         # 可在此处添加日志记录
    #         logger.error("还原源代码状态还原失败")
